/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.server.gwtspring;

import java.lang.reflect.Method;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.ServletConfigAware;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.UnexpectedException;


/**
 * Spring HandlerAdapter to dispatch GWT-RPC requests. <br>
 *
 * <a href="http://technophiliac.wordpress.com/2008/08/24/giving-gwt-a-spring-in-its-step/">http://technophiliac.
 * wordpress.com/2008/08/24/giving-gwt-a-spring-in-its-step/</a>
 *
 * @author Thibaut CASELLI
 *
 */
public class DkGwtRpcRemoteServletWrapper extends RemoteServiceServlet implements ServletConfigAware
{

	private static Log log = LogFactory.getLog(DkGwtRpcRemoteServletWrapper.class);

	private static ThreadLocal<Object> handlerHolder = new ThreadLocal<Object>();
	private static ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<HttpServletRequest>();
	private static ThreadLocal<HttpServletResponse> responseHolder = new ThreadLocal<HttpServletResponse>();

	private static final long serialVersionUID = -7421136737990135393L;

	@Autowired
	private GwtRpcMethodWrapper gwtRpcMethodWrapper;

	// Cached method
	private Method rpcWrapperMethod = null;

	// IMPLEMENTATION OF HANDLER_ADAPTER

	/**
	 * Forward request to {@link RemoteServiceServlet}
	 *
	 * @param request
	 * @param response
	 * @param handler
	 * @throws Exception
	 */
	public void handleRequest(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception
	{
		try
		{
			requestHolder.set(request);
			responseHolder.set(response);
			handlerHolder.set(handler);
			doPost(request, response);
		}
		finally
		{
			requestHolder.set(null);
			responseHolder.set(null);
			handlerHolder.set(null);
		}
	}

	//	@Override
	//	protected void doUnexpectedFailure(final Throwable e)
	//	{
	//		super.doUnexpectedFailure(e);
	//	}

	//	protected void doUnexpectedFailure(Throwable e) {
	//	    try {
	//	      getThreadLocalResponse().reset();
	//	    } catch (IllegalStateException ex) {
	//	      /*
	//	       * If we can't reset the request, the only way to signal that something
	//	       * has gone wrong is to throw an exception from here. It should be the
	//	       * case that we call the user's implementation code before emitting data
	//	       * into the response, so the only time that gets tripped is if the object
	//	       * serialization code blows up.
	//	       */
	//	      throw new RuntimeException("Unable to report failure", e);
	//	    }
	//	    ServletContext servletContext = getServletContext();
	//	    RPCServletUtils.writeResponseForUnexpectedFailure(servletContext,
	//	        getThreadLocalResponse(), e);
	//	  }

	// CUSTOM METHOD

	protected Object getCurrentHandler()
	{
		return handlerHolder.get();
	}

	/**
	 * Get current {@link HttpServletRequest}
	 *
	 * @return current {@link HttpServletRequest}
	 */
	public HttpServletRequest getRequest()
	{
		return requestHolder.get();
	}

	/**
	 * Get current {@link HttpServletResponse}
	 *
	 * @return current {@link HttpServletResponse}
	 */
	public HttpServletResponse getResponse()
	{
		return responseHolder.get();
	}

	// OVERRIDE RemoteServiceServlet
	@Override
	public String processCall(final String payload) throws SerializationException
	{
		/*
		 * The code below is borrowed from RemoteServiceServlet.processCall, with the following changes:
		 * 
		 * 1) Changed object for decoding and invocation to be the handler (versus the original 'this')
		 */

		try
		{
			if (log.isDebugEnabled())
			{
				log.debug("currentHandler = [" + getCurrentHandler().getClass() + "], payload = [" + payload);
			}
			final RPCRequest rpcRequest = RPC.decodeRequest(payload, getCurrentHandler().getClass(), this);
			if (log.isDebugEnabled())
			{
				log.debug("rpcRequest = [" + rpcRequest + "] rpcRequest.getSerializationPolicy() = [" + rpcRequest.getSerializationPolicy() + "]");
			}
			onAfterRequestDeserialized(rpcRequest);


			if (rpcWrapperMethod == null)
			{
				// Wrap call to handle exceptions and provide a way to add custom behavior around RPC calls
				rpcWrapperMethod = ReflectionUtils.findMethod(gwtRpcMethodWrapper.getClass(), "callRpcMethodInternal", Object.class, Method.class, Object[].class);
				if (rpcWrapperMethod == null)
				{
					throw new NoSuchMethodException("Internal configuration error : No method exists in [" + gwtRpcMethodWrapper.getClass().getSimpleName()
							+ "] with name [callRpcMethodInternal] and given arguments.");
				}
			}
			// Arguments to pass to wrapper : [RPC controller object, method in RPC controller, method parameters]
			final Object[] rpcWrapperArgs = new Object[] { getCurrentHandler(), rpcRequest.getMethod(), rpcRequest.getParameters() };

			// Call wrapper method with rpcWrapperArgs
			final String response = RPC.invokeAndEncodeResponse(gwtRpcMethodWrapper, rpcWrapperMethod, rpcWrapperArgs, rpcRequest.getSerializationPolicy());
			if (log.isDebugEnabled())
			{
				log.debug("response = [" + response + "]");
			}
			return response;
		}
		catch (final NoSuchMethodException e)
		{
			if (log.isErrorEnabled())
			{
				log.error("Error in GwtRpcRemoteServletWrapper :", e);
			}
			return "Internal error. Not able to get rpc wrapper method in gwtRpcMethodWrapper.";
		}
		catch (final IncompatibleRemoteServiceException ex)
		{
			if (log.isErrorEnabled())
			{
				log.error("Error in gwtRpcHandler :", ex);
			}
			return RPC.encodeResponseForFailure(null, ex);
		}
		catch (final UnexpectedException ex)
		{
			if (log.isErrorEnabled())
			{
				log.error("Error in GwtRpcRemoteServletWrapper :", ex);
			}
			return "An unexpected exception was thrown on server side. A service method, "
					+ "being invoked by GWT RPC, throws a checked exception that is not in the service method's signature. "
					+ "Such exceptions are 'unexpected' and the specific exception will be the cause of the UnexpectedException. Cause=[ "
					+ (ex.getCause() == null ? "null" : ex.getCause()) + " ]";
		}
	}

	@Override
	public void setServletConfig(final ServletConfig config)
	{
		try
		{
			log.debug("Initialize GwtRpcRemoteServletWrapper with config : " + config);
			super.init(config);
		}
		catch (final ServletException e)
		{
			log.error("Error during GwtRpcRemoteServletWrapper intitialization :", e);
		}
	}
}
